package mars.ns.core
import mars.ns.HasState
import java.io.Serializable

/**
 * Optional a list of Linkables can be given
 */
class Network[T <: Node](private var nodes: List[T]) 
		extends Iterable[T] with HasNodes with Serializable {

    override def iterator = nodes.iterator

    // constructor whitouth a set of nodes given
    def this() = this(List[T]())
    override def toString = this.nodes.toString

    def getNodes(): List[T] = this.nodes;
    
    def getNodeById(id: Int) = nodes collect { case node: T if node.id == id => node } head
    /**
     * Add one node to the network
     *  @return	True if the node is added
     */
    def addNode(node: T): Boolean =
        if (this.canHaveAsNode(node)) {
            this.nodes ::= node; return true
        }
        else {
            return false
        }
    
    /**
     * Adds a given list of nodes to the network
     * Only thos who are allowed to be added, will be added
     */
    def addNodes(nodesparam: List[T]) : Boolean = {
        var added : Int = 0;
        nodesparam.foreach { node => 
            if(this.addNode(node)){ 
                added += 1
            }
        }
        return (added == nodesparam.length)
    }
    
    /**
     * removes a node and all links to and from the node
     */
    def removeNode(node : T) : Boolean = {
    	if(!this.hasNode(node))
    	    return false
		val len : Int = this.nodes.size
        this.nodes = this.nodes filterNot (node canEqual)
    	this.nodes foreach (other => other.removeLinksTo(node))
    	node.removeAllLinks()
        return (this.nodes.size < len)
    }
    
    /**
     * adds a defaultLink between two nodes
     * @effect	this.addLink(from,to, new DefaultLink)
     */
    def addLink(from: T, to: T) : Boolean = {
        return this.addLink(from, to, new DefaultLink)
    }

    /**
     * Adds a link between the two given nodes with the given linkType
     * Note: Also adds the given nodes to the network if they are not already added
     */
    def addLink(from: T, to: T, linkType: LinkType) : Boolean = {
        if(from == null || to == null)
            return false
        this.addNode(from)
        this.addNode(to)
        return from.addLink(to, linkType)
    }

    /**
     * adds a defaultLink in two directions between two nodes
     * @effect	this.addDoubleLink(from,to, new DefaultLink)
     */
    def addDoubleLink(from: T, to: T) : Boolean = {
        return this.addDoubleLink(from, to, new DefaultLink)
    }

    /**
     * Adds a link in two directions between the two given nodes with the given linkType
     * Note: Also adds the given nodes to the network if they are not already added
     */
    def addDoubleLink(from: T, to: T, linkType: LinkType) : Boolean = {
        if(from == null || to == null || linkType == null)
            return false
        this.addNode(from)
        this.addNode(to)
        return from.addDoubleLink(to, linkType)
    }

    /**
     * Removes a link from the network
     */
    def removeLink(from: T,to: T, linkType: LinkType) : Boolean = {
        if(from == null || to == null || linkType == null)
            return false
        return from.removeLink(to,linkType)
    }

    /**
     * Removes a link in two directions from the network
     */
    def removeDoubleLink(from: T,to: T, linkType: LinkType) : Boolean = {
        return this.removeLink(from,to,linkType) && this.removeLink(to,from,linkType)
    }

    /**
     * a node can not be null and one node can not occur twice in one network
     * @return	True if the given node can be added as a node to this network
     */
    private def canHaveAsNode(node: T): Boolean = (node != null && !this.hasNode(node))

    /**
     * @return	True if the given network has a connection with this network
     * The linkType will be ignored
     */
    def isConnected(other: Network[T]): Boolean = (this.isConnected(other,null));
    
    /**
     *  @return True	if the given networks are connected via the given linkType
     * 	Note: SubClasses of the given linkType are accepted too
     */
    def isConnected(other: Network[T], linkType: LinkType): Boolean = {
        var isConnected: Boolean = false;
        for (node <- this.nodes) {
            isConnected = isConnected || node.getLinkedObjects(linkType).exists(el => other.hasNode(el))
        }
        return isConnected
    }
    /**
     *  @effect	This.getComponents(null)
     *  @return	All components, whatever the type of relationType specified is
     */
    def getComponents() : List[Network[T]] = {
        this.getComponents(null)
    }
    
    /**
     * 	Get all coherent components in the network
     * 	specified by a given linkType
     * 	Note: SubClasses of the given linkType are accepted too
     * 	@param	linkType the components are linked with
     * 	@return	A list of Components(Network[T])
     */
    def getComponents(linkType: LinkType): List[Network[T]] = {
        def merge(network: Network[T], list: List[Network[T]], linkType: LinkType): List[Network[T]] = {
            if (list.isEmpty) Nil
            else if (network.isConnected(list.head, linkType)) {
                network.addNodes(list.head.getNodes());
                merge(network, list.tail, linkType);
            }
            else list.head :: merge(network, list.tail, linkType)
        }
        // creating a lists of networks, all containing just one node
        var networks: List[Network[T]] = for {node <- this.getNodes} yield new Network[T](List(node))
        // initialize list with components
        var components: List[Network[T]] = List[Network[T]]()
        var networksize = networks.length;
        // the actual merging
        while (!networks.isEmpty) {
            var merge_network = networks.head
            networksize = networks.length;
            networks = merge(merge_network, networks.tail, linkType)
            // check if the network is updated, if not check the rest
            if (networksize - 1 == networks.length)
                components ::= merge_network
            else
                networks ::= merge_network
        }
        return components
    }

}
